#include "clientconnection.h"

ClientConnection::ClientConnection(QHostAddress a, quint16 p, bool onlyReceive) :
	ConnectionPlayer(a, p, onlyReceive)
{
	connectionHash.clear();
	connect(this, SIGNAL(newInputAdded()), this, SLOT(sendSTATE()));
	connect(this, SIGNAL(deadTimeTick()), this, SLOT(sendDEAD()));
}

ClientConnection::ClientConnection(bool onlyReceive) :
	ConnectionPlayer(onlyReceive)
{
	connectionHash.clear();
	connect(this, SIGNAL(newInputAdded()), this, SLOT(sendSTATE()));
	connect(this, SIGNAL(deadTimeTick()), this, SLOT(sendDEAD()));
}

void ClientConnection::connectToServer()
{
	if (nick != "")
	{
		connToHost();
		//connect(this->timer, SIGNAL(timeout()), this, SLOT(sendState()));
		connect(this, SIGNAL(newPacket(Packet*)), this,
				SLOT(processPacket(Packet*)));
		PacketData* toSend = new PacketData();
		toSend->appendString("HELLO");
		toSend->appendString(nick);
		sendPacket(toSend, true);
		connectionState = QAbstractSocket::ConnectingState;
		emit connectionStateChanged( connectionState);
	}
}

void ClientConnection::sendSTATE()
{
	if (connectionState == QAbstractSocket::ConnectedState)
	{
		PlayerInput playerInput = playerMoveList.last().getPlayerInput();
		PacketData* toSend = new PacketData();
		toSend->appendString("STATE");
		toSend->appendUInt(playerMoveList.last().getTime());
		toSend->appendInt(playerInput.getDir1());
		toSend->appendInt(playerInput.getDir2());
		toSend->appendReal(playerInput.getAngle());
		toSend->appendInt(playerInput.getIsShooting());
		sendPacket(toSend, false, false);
	}
}

void ClientConnection::sendDEAD()
{
	if (connectionState == QAbstractSocket::ConnectedState)
	{
		PacketData* toSend = new PacketData();
		toSend->appendString("DEAD");
		//qDebug() << "send dead";
		sendPacket(toSend, false, false);
	}
}

void ClientConnection::disconnectFromServer()
{
	PacketData* toSend = new PacketData();
	toSend->appendString("DISCONNECT");
	sendPacket(toSend, false, false);
	connectionHash.clear();
	disconnFromHost();
}

void ClientConnection::runCommand(PacketData* data, bool isNewestPacket)
{
	QString command = data->nextString();
	//if (command == "STATE") qDebug() << "newest packet" << isNewestPacket;
	//qDebug() << "Server says: " <<command;
	if (command == "HELLO")
		doHELLO(data);
	else if (command == "PING")
		doPING(data);
	else if (command == "JOIN")
		doJOIN(data);
	else if (command == "LIST")
		doLIST(data);
	else if (command == "QUIT")
		doQUIT(data);
	else if (command == "DISCONNECT")
		doDISCONNECT(data);
	else if ((command == "STATE"))
		doSTATE(data);
	else if (command == "SHOOT")
		doSHOOT(data);

	//delete data;
}

void ClientConnection::doHELLO(PacketData* data)
{
	qint32 id = data->nextInt();
	setPlayerId(id);
	setNick(getNick());
	setScore(0);
	/*emit newPlayerConnected(this);*/
	connectionState = QAbstractSocket::ConnectedState;
	startConnectionTimer(true);
	emit connectionStateChanged( connectionState);

}

void ClientConnection::doJOIN(PacketData* data)
{
	qint32 id = data->nextInt();
	QString nick = data->nextString();
	qint32 score = 0;
	ConnectionPlayer* player = new ConnectionPlayer();
	player->setNick(nick);
	player->setScore(score);
	player->setPlayerId(id);
	connectionHash.insert(id, player);
	//qDebug() << "emit newPlayerConn";
	emit newPlayerConnected(player);
}

void ClientConnection::doLIST(PacketData* data)
{
	while (!data->atEnd())
	{

		qint32 id = data->nextInt();
		QString nick = data->nextString();
		qint32 score = data->nextInt();
		ConnectionPlayer* player = new ConnectionPlayer();
		player->setNick(nick);
		player->setScore(score);
		player->setPlayerId(id);
		connectionHash.insert(id, player);
		emit newPlayerConnected(player);
	}
}

void ClientConnection::doSTATE(PacketData* data)
{
	qint32 id = data->nextInt();

	ConnectionPlayer* conn;
	bool hasPlayer = false;

	if (connectionHash.contains(id))
	{
		conn = connectionHash.value(id);
		hasPlayer = true;
	}
	else if (id == getPlayerId())
	{
		conn = this;
		hasPlayer = true;
	}
	if (hasPlayer)
	{
		//qDebug() << debCount++ << conn->getPos();

		quint32 time = data->nextUInt();
		qint32 isAlive = data->nextInt();
		quint8 dir1 = data->nextInt();
		quint8 dir2 = data->nextInt();
		qreal angle = data->nextReal();
		qint32 isShooting = data->nextInt();

		//if (conn->getIsRemote() && conn->getPlayerId()==1) qDebug()<< conn->getPlayerId() << time << isAlive << dir1 << dir2 << angle << isShooting;

		PlayerInput playerInput;

		playerInput.setDir1(dir1);
		playerInput.setDir2(dir2);
		playerInput.setAngle(angle);
		playerInput.setIsShooting(isShooting == 1);

		qreal x = data->nextReal();
		qreal y = data->nextReal();

		PlayerState playerState;
		playerState.setIsAlive(isAlive == 1);
		playerState.setPosition(QPointF(x, y));
		playerState.setVelocity(getVelocityByDirs(dir1, dir2));

		PlayerMove playerMove;
		playerMove.setTime(time);
		playerMove.setPlayerInput(playerInput);
		playerMove.setPlayerState(playerState);

		bool makeRevertRepeat = false;
		bool wasInsert = false;
		qint32 revertIndex = 0;
		int i = conn->getPlayerMoveList().count()-1;

		while (!wasInsert && i >= 0)
		{
			//if (conn->getIsRemote() && conn->getPlayerId()==1) qDebug() << playerMove.getTime() << conn->getPlayerMoveList().at(i).getTime();
			if (playerMove.getTime() >= conn->getPlayerMoveList().at(i).getTime())
			{
				if (conn->getPlayerMoveList().at(i).getTime() == playerMove.getTime()
						&& conn->getPlayerMoveList().at(i).differsFrom(playerMove))
				{
					//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "here1";

					revertIndex = i;
					conn->getPlayerMoveList().replace(revertIndex, playerMove);
					makeRevertRepeat = true;
					wasInsert = true;
				}
				else if (playerMove.getTime()  > conn->getPlayerMoveList().at(i).getTime())
				{

					revertIndex = i + 1;
					//qDebug() << "before insert count" <<conn->getPlayerMoveList().count();
					conn->getPlayerMoveList().insert(revertIndex, playerMove);
					//qDebug() << conn->getPlayerMoveList().at(revertIndex).getTime();
					//qDebug() << "after insert count" <<conn->getPlayerMoveList().count();
					//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "inserted move at "<<revertIndex<<playerMove.getTime();
					makeRevertRepeat = true;
					wasInsert = true;
					if (conn->getIsRemote() && conn->getPlayerId()==1){
						//qDebug() << "here2";
						//qDebug() << revertIndex << conn->getPlayerMoveList().count()-1;
					}
				}
				else
				{
					//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "here3";
					wasInsert = true;
				}
			}
			//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug () << wasInsert << stop << i;
			i--;
		}
		if (!wasInsert)
		{
			conn->getPlayerMoveList().append(playerMove);
			revertIndex = 0;
			makeRevertRepeat = true;
		}

		//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << debCount++ << conn->getPos();
		if (makeRevertRepeat)
		{
			//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "is make revert, found on" << revertIndex << "from all" << (conn->getPlayerMoveList().count()-1);
			if (conn->getPlayerTime() >= playerMove.getTime())
			{
				//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "is move time earlier or equal";
				if (conn->getPlayerTime() == playerMove.getTime())
				{
					//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "is equal";
					conn->setPosition(playerState.getPosition());
					conn->movePlayerBy(playerState.getVelocity());
				} else
				{
					//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "is earlier";
					quint32 currentTime	= playerMove.getTime();

					PlayerState currentState = playerMove.getPlayerState();

					conn->setPosition(currentState.getPosition());

					for (int i = (revertIndex + 1); i< conn->getPlayerMoveList().count()-1; i++)
					{
						//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "for" << i << "iteration start";
						if (conn->getPlayerTime()> conn->getPlayerMoveList().at(i).getTime())
						{
							//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "conn time greater making delta";
							quint32 deltaTime = conn->getPlayerMoveList().at(i).getTime()- currentTime;
							//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "moving player by"<< (currentState.getVelocity()* deltaTime);
							conn->movePlayerBy(currentState.getVelocity()* deltaTime);

							currentTime = conn->getPlayerMoveList().at(i).getTime();
							currentState= conn->getPlayerMoveList().at(i).getPlayerState();
						} else
						{
							//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "time exceeded";
							break;
						}
					}
				}
				/*if (conn->getIsRemote())
				{
					if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "appending to list time" << playerMove.getTime();
					conn->setPlayerTime(playerMove.getTime());
					conn->getPlayerMoveList().append(playerMove);
				}*/
			}
			else
			{
				if (conn->getIsRemote())
				{
					//if (conn->getIsRemote() && conn->getPlayerId()==1)qDebug() << "appending to list time" << playerMove.getTime();
					conn->setPlayerTime(playerMove.getTime());
					conn->getPlayerMoveList().append(playerMove);
				}
			}
		}
		//qDebug() << debCount++ << conn->getPos();
		if ((isAlive==1) && (isShooting==1))
		{
			conn->emitGunFired(x,y,angle);
		}
		if (!conn->getIsAlive() && isAlive == 1)
		{
			qDebug() << "MAKING ALIVE :)";
			conn->setPosition(QPointF(x, y));
			if (conn->getIsRemote())
			{
				conn->setPlayerTime(time);
			}
		}
		conn->setIsAlive(isAlive == 1);
	}
}

void ClientConnection::doSHOOT(PacketData* data)
{
	qint32 id = data->nextInt();
	ConnectionPlayer* conn;

	if (connectionHash.contains(id))
	{
		conn = connectionHash.value(id);
		qreal x = data->nextReal();
		qreal y = data->nextReal();
		qint32 angle = data->nextInt();

		//conn->fireGun(x,y,qreal(angle));
	}
}

void ClientConnection::doPING(PacketData* data)
{
	qint32 t = data->nextInt();
	PacketData* toSend = new PacketData();
	toSend->appendString("PONG");
	toSend->appendInt(t);
	sendPacket(toSend, false);
}

void ClientConnection::doDISCONNECT(PacketData* data)
{
	connectionHash.clear();
	disconnFromHost();
}

void ClientConnection::doQUIT(PacketData* data)
{
	qint32 id = data->nextInt();
	connectionHash.remove(id);
	emit playerDisconnected(id);
}
